//
//  CutHelper.m
//  Box2DProject
//
//  Created by Thi Huynh on 9/12/12.
//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//

#import "CutHelper.h"


@interface CutHelper (privateMethods)

- (b2Body*)createBodyWith:(b2World*)world position:(b2Vec2)position rotation:(float)rotation vertices:(b2Vec2*)vertices vertexCount:(int32)count density:(float)density friction:(float)friction restitution:(float)restitution;

- (b2Vec2*)arrangeVertices:(b2Vec2*)vertices count:(int)count;

- (b2Vec2*)arrangeVertices:(b2Vec2*)vertices count:(int)count;

- (BOOL)areVerticesAcceptable:(b2Vec2*)vertices count:(int)count;

- (float)randomFloatBetween:(float)smallNumber andBig:(float)bigNumber;

@end



int comparator(const void *a, const void *b) 
{
    const b2Vec2 *va = (const b2Vec2 *)a;
    const b2Vec2 *vb = (const b2Vec2 *)b;
    
    if (va->x > vb->x) 
        return 1;
    else if (va->x < vb->x) 
        return -1;
    return 0;    
}

@implementation CutHelper

static CutHelper* helper;
#pragma mark - 
- (id)init
{
    self = [super init];
    if(self)
    {
        //TODO : init properties here
    }
    
    return self;
}
+ (id)alloc 
{
    @synchronized ([CutHelper class])                            
    {
        NSAssert(helper == nil,
                 @"Attempted to allocated a second instance of the Main Game singleton"); 
        helper = [super alloc];
        return helper;                               
    }
    return nil;  
}
+ (CutHelper*)getInstance
{
    @synchronized([CutHelper class])    
    {
        if(!helper)                                               
            helper = [[self alloc] init];
        return helper;                                   
    }
    return nil; 
}

#pragma mark - action method
/**
 method to split all body in physic world
 */
- (void)splitBodyWith:(b2World*)world layer:(AbstractLayer*)layer atPoint:(CGPoint)point cuts:(int)cuts
{
    std::vector<CGPoint> _cutArray = [self getArrayOfCuts:point radius:100 cuts:cuts];
    
    for(int i = 0; i< _cutArray.size()/2; i +=2)
    {
        CGPoint pointA = _cutArray[i*2+0];
        CGPoint PointB = _cutArray[i*2+1];
        [self splitAllBodyWith:world layer:layer from:pointA to:PointB];
    }
    
}
/**
 method to split all body in physic world
 */
- (void)splitAllBodyWith:(b2World*)world layer:(AbstractLayer*)layer from:(CGPoint)start to:(CGPoint)end
{
    AllBodiesRayCastCallback callback1;
    world->RayCast(&callback1, 
                   [MathHelper toMeters:start], 
                   [MathHelper toMeters:end]);
    
    std::map<b2Body*, b2Vec2>::iterator it;
    for(it = callback1.rayCastInfo.begin(); it != callback1.rayCastInfo.end(); it++)
    {
        b2Body* key = (*it).first;
            
        PolygonObject* sprite = (PolygonObject*)key->GetUserData();
        if(sprite)
        {
            RaycastCallback* _raycastCallback = new RaycastCallback();
            
            // Add at the end of the ccTouchesEnded method
            world->RayCast(_raycastCallback, 
                           b2Vec2(start.x / PTM_RATIO, start.y / PTM_RATIO),
                           b2Vec2(end.x / PTM_RATIO, end.y / PTM_RATIO));
            
            world->RayCast(_raycastCallback, 
                           b2Vec2(end.x / PTM_RATIO, end.y / PTM_RATIO),
                           b2Vec2(start.x / PTM_RATIO, start.y / PTM_RATIO));
            [self splitBodyWith:world layer:layer sprite:sprite ];                
        }            
        
    }
 
        
}

/**
 method to split body from 1 to 2 body
 */
- (void)splitBodyWith:(b2World*)world layer:(AbstractLayer*)layer  sprite:(PolygonObject*)sprite //from:(CGPoint)start to:(CGPoint)end
{
    //declare & initialize variables to be used for later
    PolygonObject *newSprite1, *newSprite2;
    
    //our original shape's attributes
    b2Fixture *originalFixture = sprite.body->GetFixtureList();
    b2PolygonShape *originalPolygon = (b2PolygonShape*)originalFixture->GetShape();
    int vertexCount = originalPolygon->GetVertexCount();
    
    for (int i = 0 ; i < vertexCount; i++)
    {
        b2Vec2 point = originalPolygon->GetVertex(i);
    }
    
    //our determinant(to be described later) and iterator
    float determinant;
    int i;
    
    //we store the vertices of our two new sprites here
    b2Vec2 *sprite1Vertices = (b2Vec2*)calloc(24, sizeof(b2Vec2));
    b2Vec2 *sprite2Vertices = (b2Vec2*)calloc(24, sizeof(b2Vec2));
    b2Vec2 *sprite1VerticesSorted, *sprite2VerticesSorted;
    
    //we store how many vertices there are for each of the two new sprites here
    int sprite1VertexCount = 0;
    int sprite2VertexCount = 0;
    
    
    //step 1:
    //the entry and exit point of our cut are considered vertices of our two new shapes, so we add these before anything else
    sprite1Vertices[sprite1VertexCount++] = sprite.startSlicePoint;
    sprite1Vertices[sprite1VertexCount++] = sprite.endSlicePoint;
    sprite2Vertices[sprite2VertexCount++] = sprite.startSlicePoint;
    sprite2Vertices[sprite2VertexCount++] = sprite.endSlicePoint;
    
    //step 2:
    //iterate through all the vertices and add them to each sprite's shape
    for (i=0; i<vertexCount; i++)
    {
        //get our vertex from the polygon
        b2Vec2 point = originalPolygon->GetVertex(i);
        
        //we check if our point is not the same as our entry or exit point first
        b2Vec2 diffFromEntryPoint = point - sprite.startSlicePoint;
        b2Vec2 diffFromExitPoint = point - sprite.endSlicePoint;
        
        if ((diffFromEntryPoint.x == 0 && diffFromEntryPoint.y == 0) || (diffFromExitPoint.x == 0 && diffFromExitPoint.y == 0))
        {
        }
        else 
        {
            determinant = calculate_determinant_2x3(sprite.startSlicePoint.x, sprite.startSlicePoint.y, sprite.endSlicePoint.x, sprite.endSlicePoint.y, point.x, point.y);
            
            if (determinant > 0)
            {
                //if the determinant is positive, then the three points are in clockwise order
                sprite1Vertices[sprite1VertexCount++] = point;
            }
            else
            {
                //if the determinant is 0, the points are on the same line. if the determinant is negative, then they are in counter-clockwise order
                sprite2Vertices[sprite2VertexCount++] = point;
                
            }//endif
        }//endif
    }//endfor
    
    //step 3:
    //Box2D needs vertices to be arranged in counter-clockwise order so we reorder our points using a custom function
    sprite1VerticesSorted = [self arrangeVertices:sprite1Vertices count:sprite1VertexCount];
    sprite2VerticesSorted = [self arrangeVertices:sprite2Vertices count:sprite2VertexCount];
    
    //step 4:
    //Box2D has some restrictions with defining shapes, so we have to consider these. We only cut the shape if both shapes pass certain requirements from our function
    BOOL sprite1VerticesAcceptable = [self areVerticesAcceptable:sprite1VerticesSorted count:sprite1VertexCount];
    BOOL sprite2VerticesAcceptable = [self areVerticesAcceptable:sprite2VerticesSorted count:sprite2VertexCount];
    
    //step 5:
    //we destroy the old shape and create the new shapes and sprites
    if (sprite1VerticesAcceptable && sprite2VerticesAcceptable)
    {
        //create the first sprite's body        
        b2Body *body1 = [self createBodyWith:world position:sprite.body->GetPosition() rotation:sprite.body->GetAngle() vertices:sprite1VerticesSorted vertexCount:sprite1VertexCount density:originalFixture->GetDensity() friction:originalFixture->GetFriction() restitution:originalFixture->GetRestitution()];
        //create the first sprite
        newSprite1 = [[PolygonObject alloc]initWithFileName:sprite.fileName body:body1 vertices:sprite1VerticesSorted verticeCnt:sprite1VertexCount  destroyed:NO];
        [layer addChild:newSprite1 z:1];
        
        
        //create the second sprite's body
        b2Body *body2 = [self createBodyWith:world position:sprite.body->GetPosition() rotation:sprite.body->GetAngle() vertices:sprite2VerticesSorted vertexCount:sprite2VertexCount density:originalFixture->GetDensity() friction:originalFixture->GetFriction() restitution:originalFixture->GetRestitution()];
        //create the second sprite
        newSprite2 = [[PolygonObject alloc]initWithFileName:sprite.fileName body:body2 vertices:sprite2VerticesSorted verticeCnt:sprite2VertexCount  destroyed:NO];
        [layer addChild:newSprite2 z:1];

        //we don't need the old shape & sprite anymore so we either destroy it or squirrel it away
        if (sprite.isDestroyed)
        {   
            /*[sprite deactivateCollisions];
            sprite.position = ccp(-256,-256);   //cast them faraway
            sprite.sliceEntered = NO;
            sprite.sliceExited = NO;
            sprite.entryPoint.SetZero();
            sprite.exitPoint.SetZero();*/
            world->DestroyBody(sprite.body);
            [layer removeChild:sprite cleanup:YES];
        }
        
    }
    else
    {
        sprite.isStartSlice = NO;
        sprite.isEndSlice = NO;
    }
    
    //free up our allocated vectors
    free(sprite1VerticesSorted);
    free(sprite2VerticesSorted);
    free(sprite1Vertices);
    free(sprite2Vertices);
}

#pragma mark - private section

/**
 Method to get the random cut array from a point
 */
- (float)randomFloatBetween:(float)smallNumber andBig:(float)bigNumber 
{
    float diff = bigNumber - smallNumber;
    return (((float) (arc4random() % ((unsigned)RAND_MAX + 1)) / RAND_MAX) * diff) + smallNumber;
}

- (std::vector<CGPoint>)getArrayOfCuts:(CGPoint)center radius:(float)radius cuts:(int)cuts
{
    std::vector<CGPoint> cutArray;    
    for(int i = 0; i < cuts; ++i)
    {
        float cutAngle = [self randomFloatBetween:0 andBig:360];
        
        float x = center.x + radius * cos (cutAngle);
        float y = center.y + radius * sin (cutAngle);
        
        float x1 = center.x - radius * cos (cutAngle);
        float y1 = center.y - radius * sin (cutAngle);
        NSLog(@"[x=%f][y=%f]  -- [x1=%f][y1=%f]",x,y,x1,y1);
        cutArray.push_back(CGPointMake(x, y));
        cutArray.push_back(CGPointMake(x1, y1));
    }

    
    return cutArray;
}
/**
 Returns an sprite polygon with body and additional properties.
 */
- (b2Body*)createBodyWith:(b2World*)world position:(b2Vec2)position rotation:(float)rotation vertices:(b2Vec2*)vertices vertexCount:(int32)count density:(float)density friction:(float)friction restitution:(float)restitution
{
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.position = position;
    bodyDef.angle = rotation;
    b2Body *body = world->CreateBody(&bodyDef);
    
    b2FixtureDef fixtureDef;
    fixtureDef.density = density;
    fixtureDef.friction = friction;
    fixtureDef.restitution = restitution;
    
    b2PolygonShape shape;
    shape.Set(vertices, count);
    fixtureDef.shape = &shape;
    body->CreateFixture(&fixtureDef);
    
    return body;
}

- (b2Vec2*)arrangeVertices:(b2Vec2*)vertices count:(int)count
{
    float determinant;
    int iCounterClockWise = 1;
    int iClockWise = count - 1;
    int i;
    
    b2Vec2 referencePointA,referencePointB;
    b2Vec2 *sortedVertices = (b2Vec2*)calloc(count, sizeof(b2Vec2));
    
    //sort all vertices in ascending order according to their x-coordinate so we can get two points of a line
    qsort(vertices, count, sizeof(b2Vec2), comparator);
    
    sortedVertices[0] = vertices[0];
    referencePointA = vertices[0];          //leftmost point
    referencePointB = vertices[count-1];    //rightmost point
    
    //we arrange the points by filling our vertices in both clockwise and counter-clockwise directions using the determinant function
    for (i=1;i<count-1;i++)
    {
        determinant = calculate_determinant_2x3(referencePointA.x, referencePointA.y, referencePointB.x, referencePointB.y, vertices[i].x, vertices[i].y);
        if (determinant<0)
        {
            sortedVertices[iCounterClockWise++] = vertices[i];
        }
        else 
        {
            sortedVertices[iClockWise--] = vertices[i];
        }//endif
    }//endif
    
    sortedVertices[iCounterClockWise] = vertices[count-1];
    return sortedVertices;
}


- (BOOL)areVerticesAcceptable:(b2Vec2*)vertices count:(int)count
{
    //check 1: polygons need to at least have 3 vertices
    if (count < 3)
    {
        return NO;
    }
    
    //check 2: the number of vertices cannot exceed b2_maxPolygonVertices
    if (count > b2_maxPolygonVertices)
    {
        return NO;
    }
    
    //check 3: Box2D needs the distance from each vertex to be greater than b2_epsilon
    int32 i;
    for (i=0; i<count; ++i)
    {
        int32 i1 = i;
        int32 i2 = i + 1 < count ? i + 1 : 0;
        b2Vec2 edge = vertices[i2] - vertices[i1];
        if (edge.LengthSquared() <= b2_epsilon * b2_epsilon)
        {
            return NO;
        }
    }
    
    //check 4: Box2D needs the area of a polygon to be greater than b2_epsilon
    float32 area = 0.0f;
    
    b2Vec2 pRef(0.0f,0.0f);
    
    for (i=0; i<count; ++i)
    {
        b2Vec2 p1 = pRef;
        b2Vec2 p2 = vertices[i];
        b2Vec2 p3 = i + 1 < count ? vertices[i+1] : vertices[0];
        
        b2Vec2 e1 = p2 - p1;
        b2Vec2 e2 = p3 - p1;
        
        float32 D = b2Cross(e1, e2);
        
        float32 triangleArea = 0.5f * D;
        area += triangleArea;
    }
    
    if (area <= 0.0001)
    {
        return NO;
    }
    
    //check 5: Box2D requires that the shape be Convex.
    float determinant;
    float referenceDeterminant;
    b2Vec2 v1 = vertices[0] - vertices[count-1];
    b2Vec2 v2 = vertices[1] - vertices[0];
    referenceDeterminant = calculate_determinant_2x2(v1.x, v1.y, v2.x, v2.y);
    
    for (i=1; i<count-1; i++)
    {
        v1 = v2;
        v2 = vertices[i+1] - vertices[i];
        determinant = calculate_determinant_2x2(v1.x, v1.y, v2.x, v2.y);
        //we use the determinant to check direction from one point to another. A convex shape's points should only go around in one direction. The sign of the determinant determines that direction. If the sign of the determinant changes mid-way, then we have a concave shape.
        if (referenceDeterminant * determinant < 0.0f)
        {
            //if multiplying two determinants result to a negative value, we know that the sign of both numbers differ, hence it is concave
            return NO;
        }
    }
    v1 = v2;
    v2 = vertices[0]-vertices[count-1];
    determinant = calculate_determinant_2x2(v1.x, v1.y, v2.x, v2.y);
    if (referenceDeterminant * determinant < 0.0f)
    {
        return NO;
    }
    return YES;
}


@end
